
/*****************************************************************************\
 *      A small programm, which shows how to use collsision detection
\*****************************************************************************/

/*! @file
 *
 *  @brief A ball roles throw the other balls and print 'Hello World' on every
 *  	   collsision
 *
 *  @author Tobias Ehlgen
 *  -reimplemented by Weiyu Yi, wyi@tu-clausthal.de
 *
 *  @todo
 *    - the ground is only a box, wouldn't it better to implement a plane
 *      instead of the box
 *    - is the value verbose in col::init still used?
 *
 *  @flags
 *
 */


//---------------------------------------------------------------------------
//  Includes
//---------------------------------------------------------------------------

//include c++ basic function
#include <stdio.h>
#include <stdlib.h>

#if defined(__APPLE__)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

//include collision detection
#include <Collision.h>
#include <ColUtils.h>
#include <ColUtilsOpenGL.h>

using col::Quat;
using col::ColGeometry;
using col::Primitive;
using col::Cube;
using col::Icosahedron;
using col::Trackball;

using col::Point3;
using col::Vector3;
using col::Vector4;
using col::Matrix4;

using col::makeCube;
using col::makeSphere;
using col::makePlane;
using col::drawGeometry;

/***************************************************************************\
 *                        Global variables                                 *
\***************************************************************************/

namespace
{

col::CollisionPipeline* colpipeline;
const int Space =2;         //Space among the balls

//variables which are used by the program to move the balls
unsigned int nobjects = 5;
int rightbound = nobjects * Space;  //max x-value
int leftbound = -2;                //min x-value
int direction = 0;
double angle = 10/3.1415926;

// ground
double ground_color[3] = { 0.9, 0.9, 0.9 };
ColGeometry ground;
Matrix4 ground_matrix;

//set the level of detail for the collision detection
col::LevelOfDetectionE level_of_det = col::LEVEL_EXACT;

//render options
bool animation_started = false;
bool lastBallRotate = true;
bool allballsRotate = false;

typedef enum
{
	PERSPECTIVE,
	ORTHOGRAPHIC
} ProjectionE;

vector<const std::vector<col::Point3> *> fixed_points;
vector<const std::vector<col::Point3> *> moving_points;

// render options
GLuint w, h;
ProjectionE proj = PERSPECTIVE;
GLenum fill_mode = GL_LINE;

Trackball tball;
GLdouble view_distance = 15.0;
Matrix4 viewing_matrix;

// pseudo-random number generator (instead of drand48)
col::FibRand *      prng;

}

/***************************************************************************\
 * MovingObj
 *
 * this struct represents the balls
\***************************************************************************/

struct MovingObject
{
	MovingObject();
	void draw() const;

    //state
	col::ColID m_id;
	ColGeometry geom;
	Matrix4 translate;
	Matrix4 rotate;
	double color[3];
};

MovingObject::MovingObject( void )
{
	translate = Matrix4::identity();
	rotate = Matrix4::identity();
}

/** 
 * @brief 
 *   OpenGL wrapper function to draw the geometry of the moving obj
 */
void MovingObject::draw() const
{
	glPushMatrix();

	Matrix4 transformMatrix = translate * rotate;
	GLdouble mat[16];
	for ( unsigned int i=0; i<4; i++ )
	for ( unsigned int j=0; j<4; j++ )
		mat[ i*4+j ] = transformMatrix[i][j];
	glMultMatrixd( mat );
	col::drawGeometry( &geom, fill_mode, color );

	glPopMatrix();
}

MovingObject* movingObject;


/******************************************************************************\
 * Callback class for collision detection
 *
 * If two objects, which register in the collision detection with this
 * callback class, are collide the operator ()  is called.
 * So everything you want to happen on collision can be write in this
 * function.
 * For more information about how to use the collision detection with more
 * than one Callback-Class see bowling.cpp.
\******************************************************************************/

class CollisionCallback : public col::Callback
{
public:
	CollisionCallback( MovingObject obj1, MovingObject obj2,
					   col::LevelOfDetectionE level );
    virtual ~CollisionCallback();
    virtual void operator () ( const col::Data *data ) throw ();
protected:
    MovingObject obj1;
    MovingObject obj2;
};


CollisionCallback::CollisionCallback( MovingObject inobj1, MovingObject inobj2,
									 col::LevelOfDetectionE inlevel )
:	col::Callback( inobj1.m_id, inobj2.m_id, false, true, inlevel ), 
	obj1(inobj1), obj2(inobj2)
{
}

CollisionCallback::~CollisionCallback()
{
}

//here you can implement the things which should happen on collision,
//in this example it isn't that much
void CollisionCallback::operator () (const col::Data* /*data*/) throw ()
{
    printf( "Collision detected: %d <---> %d \n", obj1.m_id, obj2.m_id );
    fflush(stdout);
}


/***************************************************************************\
 *                       Check and  process coll                           *
\***************************************************************************/

void move_and_check( void )
{
	Vector3 rotDir;
	double moveX;

    //move the last ball form one side to the other
    if (direction == 0)
    {
        moveX = -0.01;
        rotDir = normalize( Vector3( 1.0, 1.0, 1.0 ) );
    }
    else
    {
        moveX = 0.01;
        rotDir = normalize( Vector3( -1.0, -1.0, -1.0 ) );
    }
	
    //move the last ball
	movingObject[nobjects-1].translate[3][0] += moveX;

    if( movingObject[nobjects-1].translate[3][0] > rightbound)
        direction = 0;
    if( movingObject[nobjects-1].translate[3][0] < leftbound)
        direction = 1;

	//rotate the ball
	angle += 1.0/3.1415926;
	Matrix4 rotationMatrix = Matrix4::rotation( angle, rotDir );

    //should the rolling ball rotate
    if(lastBallRotate)
    {
		movingObject[nobjects-1].rotate = rotationMatrix;
    }

    //all balls start rotating
    if(allballsRotate)
    {
        for(unsigned int i=0; i<nobjects-1; i++)
        {
			movingObject[i].rotate = rotationMatrix;
        }
    }
    
	//update pipeline
	for ( unsigned int i=0; i<nobjects; i++ )
	{
		void * tran = static_cast<void*>(& (movingObject[i].translate) );
		colpipeline->moveObject( movingObject[i].m_id, tran, NULL );
	}
	
    //call collision detection
    colpipeline->check();
}


/***************************************************************************\
 *                       glut render process                               *
\***************************************************************************/

void init(void) 
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);
	glEnable(GL_DEPTH_TEST);
}

void reshape( int width, int height )
{
	w = width;
	h = height;
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	tball.setViewport( w, h );
}

void animate( void )
{
	if ( animation_started == false )
		return;

	move_and_check();
	glutPostRedisplay();
}

void display(void)
{
	// trackball 
	if ( tball.getTrackMode() == col::TRACK_ROTATE )
	{
		REAL angle;
		REAL axis[3];
		tball.getRotation( axis, angle );
		Matrix4 m = Matrix4::rotation( angle, col::Vector3( axis[0], axis[1], axis[2] ) );
		viewing_matrix = m * viewing_matrix;
	}
	else
	if ( tball.getTrackMode() == col::TRACK_ZOOM )
	{
		REAL zoom;
		tball.getZoom( zoom );
		Matrix4 m = Matrix4::scale( col::Vector3( zoom+1 ) );
		viewing_matrix = m * viewing_matrix;
	}

	// all geometries are ready to render
	// --------------------------------------------------------
	// display begins
	
	// Prjection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();

	GLdouble range=20.0;
	switch( proj )
	{
		case ORTHOGRAPHIC:
			// orthographic projection
			if (w <= h)
				glOrtho(-1*range, range, -1*range*(GLdouble)h/(GLdouble)w,
						range*(GLdouble)h/(GLdouble)w, 0.1, 100.0);
			else
				glOrtho(-1*range*(GLdouble)w/(GLdouble)h,
						range*(GLdouble)w/(GLdouble)h, -1*range, range, 0.1, 100.0);
			break;
		case PERSPECTIVE:
			// perspective projection
			gluPerspective(60, (GLdouble)w/(GLdouble)h, 0.1, 100.0);
			break;
		default:
			fprintf( stderr, "No such projection mode!\n" );
			break;
	}

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// View Transformation
	glTranslated( 0.0, 0.0, -view_distance );

	GLdouble view_mat[16];
	for ( unsigned int i=0; i<4; i++ )
	for ( unsigned int j=0; j<4; j++ )
		view_mat[ i*4+j ] = viewing_matrix[i][j];
	glMultMatrixd( view_mat );

	// Modelling Transformation
	
	// draw ground 
	glPushMatrix();
	GLdouble mat[16];
	for ( unsigned int i=0; i<4; i++ )
	for ( unsigned int j=0; j<4; j++ )
		mat[ i*4+j ] = ground_matrix[i][j];
	glMultMatrixd( mat );
	drawGeometry( &ground, GL_FILL, ground_color );
	glPopMatrix();

	// draw moving objs
	for ( unsigned int i=0; i<nobjects; i++ )
	{
		movingObject[i].draw();
	}

	//
	glutSwapBuffers();
}

void mouseClick( int button, int state, int x, int y )
{
	tball.mouseClick( button, state, x, y );
}

void mouseMoving( int x, int y )
{
	tball.mouseMoving( x, y );
	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	static int phase = -1;

	switch (key) {
		case 27:
			exit(0);
			break;
		case ' ':
			if ( animation_started == false )
			{
				animation_started = true;
				glutIdleFunc( animate );
			}
			else
			if ( animation_started == true )
			{
				animation_started = false;
				glutIdleFunc( NULL );
			}
			break;
		case 'p':
		case 'P':
			if ( proj == PERSPECTIVE )
				proj = ORTHOGRAPHIC;
			else
			if ( proj == ORTHOGRAPHIC )
				proj = PERSPECTIVE;

			glutPostRedisplay();
			break;
		case 'i':
		case 'I':
			if ( fill_mode == GL_LINE )
				fill_mode = GL_FILL;
			else
			if ( fill_mode == GL_FILL )
				fill_mode = GL_POINT;
			else
			if ( fill_mode == GL_POINT )
				fill_mode = GL_LINE;

			glutPostRedisplay();
			break;
		default:
			break;
	}
}


/***************************************************************************\
 * Create Scene
\***************************************************************************/

void createScene( void )
{
	double rad = 0.5;

	col::makePlane( &ground, 100, 100, 1, 1 );
	ground_matrix = Matrix4::identity();
	ground_matrix.setTranslation( Vector3( 0, 0, -rad ) );

    for (unsigned int i = 0; i < nobjects;i++)
    {
        //makeSphere creates a node and core
		col::makeSphere( & ( movingObject[i].geom ), 1, rad );

        //the transformation to set the spheres in a gain
		movingObject[i].translate.setTranslation( Vector3( i*Space, 0, 0 ) );

		movingObject[i].color[0] = prng->frand();
		movingObject[i].color[1] = prng->frand();
		movingObject[i].color[2] = prng->frand();
    }
}

/***************************************************************************\
 *                              Main                                       *
\***************************************************************************/

int main(int argc, char **argv)
{
	// initialize global and static data
    prng = new col::FibRand( time(NULL) );
	col::DopTree::init();
	viewing_matrix = Matrix4::identity();
	
    //init movingObjects
    movingObject = new MovingObject[nobjects];

	// initialize scene
	createScene();

    //init collision detection
    colpipeline = new col::CollisionPipeline ("RollingBallCollisionThread"/*ThreadName*/,1 /*ThreadID*/);

    colpipeline->setUseHulls(false);
    colpipeline->verbose(false,false);

    //register objects
    for (unsigned int i=0; i<nobjects; i++)
	{
		movingObject[i].m_id = colpipeline->makeCollidable( static_cast<void *>( & ( movingObject[i].geom ) ), NULL );
    }

    //register callbacks
    //here you can specify which callback class should be use.
    for ( unsigned int i=0; i<nobjects; i++ )
    {
        for ( unsigned int j=0;j<i; j++ )
        {
            colpipeline->addCallback( new CollisionCallback( movingObject[j], movingObject[i],
                                                             level_of_det) );
        }
    }

	// prepare glut
	glutInit( &argc, argv );
	glutInitDisplayMode ( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB );
	glutInitWindowSize( 350, 350 ); 
	glutInitWindowPosition( 100, 100 );
	glutCreateWindow( argv[0] );
	init();

	// register glut callback
	glutDisplayFunc( display ); 
	glutReshapeFunc( reshape );
	glutMouseFunc( mouseClick );
	glutMotionFunc( mouseMoving );
	glutKeyboardFunc( keyboard );

    glutMainLoop();

    return 0;
}
